'use strict';
/**
 * The main file
 */
var fs = require('fs');
var path = require('path');
var util = require('util');
var yeoman = require('yeoman-generator');
var yosay = require('yosay');
var wiredep = require('wiredep');
var chalk = require('chalk');
var glob = require('glob');
var htmlWiring = require("html-wiring");
var isInstalled = require('is-installed');
var ncp = require('ncp').ncp;
    ncp.limit = 16;
var exec = require('child_process').exec;
var _ = require('underscore');
_.mixin(require('underscore.inflections'));
// our lib
var ngUtils = require('../../lib/utils');
var Dot = require('../../lib/dot');
var preference = require('../../lib/preference');
var password = require('../../lib/password');
var passportConfig = require('../../lib/passport');
var databaseConfig = require('../../lib/database');
// etc
var eng2cn = {'development': '开发' , 'production': '生产'};

/**
 * Constructor
 */
var Generator = module.exports = function(args, options)
{
	yeoman.generators.Base.apply(this, arguments);
	// bunch of init setup
	this.argument('appname', { type: String, required: false });
	// set this up to hold the answers
	this.answers = {};
	// get app name etc
	this._getAppName();
	// setup other options
	this._setupOptions();

	// when this end final callback
  	this.on('end', function () {
        this._runFinalSetup();
  	});
  	//console.log(path.resolve('../../package.json'));
	// set some based path and get info from package.json
  	this.pkg = require('../../package.json');
  	this.sourceRoot(path.join(__dirname, path.join('..','templates','common')));
  	// show the version - this is wrong package.json
  	// this.log(chalk.blue('Your generator-panesjs vesrion is: ' + this.pkg.version));
};

/**
 * extending base class
 */
util.inherits(Generator, yeoman.generators.Base);

/**
 * additional code to be call one after the other
 * this whole thing could be removed
 */
Generator.prototype.welcome = function()
{
  	if (!this.options['skip-welcome-message']) {
        var lang = this.env.options.lang;
        var hello = (lang==='cn') ? '主人，很荣幸可以为你效劳' : 'Glad I can help, my lord.';
        var second = chalk.magenta('Yo Generator for panes.js brought to you by ') + chalk.white('panesjs.com' + '\n');
        if (lang==='cn') {
            second = chalk.magenta('由') + chalk.white('panesjs.com') + chalk.magenta('提供的node.js MVC框架开发協助工具\n');
        }
    	this.log(yosay(hello));
    	this.log(second);
  	}
};

	////////////////////////////////
	//	      QUESTIONS           //
	////////////////////////////////

/**
	1. app name via the options (want to confirm?)
	2. Database - RDBMS NoSQL NewSQL
	3. Ask for credential?
	4. Generate front end?
**/
/**
 * new feature 2015-08-26 check if there was previously setup project
 */
Generator.prototype.checkForPreviousProject = function()
{
    var _this = this;
    var lang = _this.env.options.lang;
    var cb = _this.async();
    //console.log('call checkForPreviousProject');
    preference.init(lang).then(function(result)
    {
        /*
        @TODO: list all the available previous project then select it to create a new project etc
        */
        cb();
    });
};

/**
 * We ask for the appName again only when the user didn't supply one
 */
Generator.prototype.askForAppName = function()
{
	var _this = this;
	if (this.baseNameOption) {
		var cb = this.async();
		var appName = this.env.options.appNameAgain;
		var msg = (this.env.options.lang==='cn') ? '你现时的项目名是:`' + appName + '`, 你想修改吗？'
												 : 'Your appname is: `' + appName + '`, would you like to change it?';
		this.prompt({
	        type: 'input',
	        name: 'appname',
	        message: msg,
	        default: appName
	    }, function(props) {
	        _this._getAppName(props.appname);
	        cb();
	    }.bind(this));
	}
};

/**
 * Ask if the user want to use Docker or Vagrant
 */
Generator.prototype.askIfWantToUseDockerOrVagrant = function()
{
	var _this = this;
	var cb = this.async();
	var lang = _this.env.options.lang;
	var msg = (lang==='cn') ? '你想用Docker或Vagrant来邦助开发吗?' : 'Would you like to use Docker or Vagrant for development?';
	var list = [{name: 'No' , value: 'n'} , {name: 'Vagrant' , value: 'v'} , {name: 'Docker' , value: 'd'}];
	if (lang==='cn') {
		list[0].name = '不用';
	}
	this.prompt({
		type: 'list',
		choices: list,
		name: 'virtual',
		message: msg,
		default: 'n'
	}, function(props) {
		_this.answers.virtual = props.virtual;
		_this.answers.useVagrant = (props.virtual==='v');
		_this.answers.useDocker  = (props.virtual==='d');
		cb();
	})
};

/**
 * setup passport module , by default we set it up with local
 * we are basically just calling a panesjs sub generator passport
 * 2015-08-26 couldn't use the sub module to ask question here, it will break the chain
 */
Generator.prototype.askForPassportModules = function()
{
	var _this = this;
 	var cb = this.async();
	var lang = this.env.options.lang;
 	var msg = (lang==='cn') ? '选其他第三方登彔方式插件(passport strategies,local己经自帶)' : 'Choose other passport strategies (local already included)';
	var list = passportConfig.getStrategies(lang);
	_this.answers.passportModules = [];
 	this.prompt({
 		type: 'checkbox',
 		name: 'passportModules',
 		message: msg,
 		choices: list
 	}, function(props)
 	{
		var hasMod = function (mod) {
		    return props.passportModules.indexOf(mod) !== -1;
		};
		list.forEach(function(_mod_) {
            if (hasMod(_mod_.value)) {
                _this.answers.passportModules.push(_mod_);
            }
        });
		cb();
 	}.bind(this));
};

/**
 * socket.io
 */
Generator.prototype._TODO_askForSocket = function()
{
    var _this = this;
    var cb = _this.async();
    var lang = _this.env.options.lang;
	// 在完成设置后台后
	var msg = (lang==='cn') ? '你想使用实时网socket.io吗？' : 'Would you like to use socket.io?';
	this.prompt({
		type: 'confirm',
		name: 'socket',
		message: msg,
		default: true
	},function(props)
	{
		_this.answers.socket = props.socket;
		// generate the database config here
        _this.answers.databases = {'socket': {databaseType: 'NOSQL' , databaseDriver: 'redis'}};
        var msg = (lang==='cn') ? '自动生成一个Redis数据库设置名为`socket`.' : 'Setting up a redis profile name `socket`.';
        _this.log(chalk.yellow(msg));
        _this.log('---------------------------------------');
		cb();
	}.bind(this));
};

/**
 * Top level ask for database question method
 */
Generator.prototype.askForDatabase = function()
{
	var _this = this;
	var cb = _this.async();
	// try something new - only pass one cb all the way
	_this._askForConnectionName(cb);

};

    ///////////////////////////////////
    //  DATABASES RELATED QUESTIONS  //
    ///////////////////////////////////

/**
 * ask for the profile name
 */
Generator.prototype._askForConnectionName = function(cb)
{
	var _this = this;
	var lang = _this.env.options.lang;
	if (_.isUndefined(_this.answers.databases) || _.isUndefined(_this.answers.databases.default)) {
		var msg = (lang==='cn') ? '建立你的默认数据库设定`default`' : 'Setting up your `default` database profile';
		_this.log(chalk.yellow(msg));
		_this.answers.databases = {'default': {}};
		_this._askForDatabaseType(cb , 'default');
	}
	else { // this is a coming back
		var msg = (lang==='cn') ? '请提供一个新的数据库设定名稱 [例如 profile1]' : 'Please supply a database profile name [i.e. profile1].';
		this.prompt({
			type: 'input',
			name: 'profile',
			message: msg
		}, function(props) {
			var profile = props.profile;
			if (_.isUndefined(_this.answers.databases[profile])) {
				_this.answers.databases[profile] = {};
				_this._askForDatabaseType(cb , profile);
			}
			else {
				_this.log.error(chalk.red('`' + profile + '`' + ((lang==='cn') ? '已经使用，请提供一个新的名稱!' : ' already existed! Please pick another name!')));
				_this._askForConnectionName(cb);
			}
		}.bind(this));
	}
};

/**
 * ask for database engine, this is only thing we need to ask for database for now
 * 15.8.2015 - breaking change, we now support multiple database. So user can add more than one connection profile
 */
Generator.prototype._askForDatabaseType = function(cb , profile)
{
	var _this = this;
	var list = [{name: 'NoSQL (Mongodb , CouchDB , Redis)' , value: 'NOSQL'},
                {name: 'RDBMS (MySQL, Postgresql, Sqlite, MSSql)' , value: 'RDBMS'}
				]; // 13.9.2015 add CrouchDB next
				// {name: 'NewSQL*' , value: 'NEWSQL'}]; // disable for now until we make it work
	var lang = _this.env.options.lang;
	var msg = (lang==='cn') ? '你想用那一个类型的数据库?' : 'What kind of database would you like to use?';

	this.prompt({
		type: 'list',
		name: 'databaseType',
		choices: list,
		message: msg,
		default: 'NOSQL'
	}, function(props)
	{
		var selected = props.databaseType;
		if (selected==='NEWSQL') {
			var msg = (lang==='cn') ? '>_< 现时还在努力写代码中，很快会支持NewSql，请再选.'
									: 'NewSql is not available yet. Coming soon! Please pick again.';
			this.log.error(chalk.red(msg));
			_this._askForDatabaseType(); // again!
		}
		else {
			this.answers.databases[profile].databaseType = selected;
			// for templates
			this.answers.databases[profile].database = {
				'RDBMS': (selected==='RDBMS'),
				'NOSQL': (selected==='NOSQL'),
				'NEWSQL': (selected==='NEWSQL')
			};
			_this._askForDatabaseDriverQuestion(cb , profile);
		}
	}.bind(this));
};

/**
 * ask for database driver, same with database type they can only use one
 */
Generator.prototype._askForDatabaseDriverQuestion = function(cb , profile)
{
	var _this = this;
	var lang = _this.env.options.lang;
	var msg = (lang==='cn') ? '你想用那一个数据库' : 'Which database are you going to use?';
	var errorMsg = (lang==='cn') ? '数据库' : 'You didn\'t select a database, please select again!';
	var list = [];
	var d = '';
	switch (_this.answers.databases[profile].databaseType) {
		case 'RDBMS':
			list = ['MySQL' , 'MariaDB' , 'Postgresql' , 'SQLite'];
			d = 'MySQL';
		break;
		case 'NOSQL':
			list = ['MongoDB' , 'CouchDB' , 'Redis'];
			d = 'MongoDB';
		break;
		case 'NEWSQL':
			list = ['SequoriaDB'];
			d = 'SequoriaDB';
		break;
		default:
			// nothing to do
	}
	// ask the questions
	_this.prompt({
		type: 'list',
		choices: list,
		message: msg,
		name: 'databaseDriver',
		default: d
	}, function(props)
	{
		var d = props.databaseDriver.toLowerCase();
		_this.answers.databases[profile].databaseDriver = d;
		if (d==='sqlite') {
			_this.answers.databases[profile].useSqlite = true;
			_this.answers.useSqlite = true;
		}
		_this._askIfDatabaseSelectionDone(cb , profile);
	}.bind(_this));
};

/**
 * the actual become a sub command, we run this in a loop until they say its done,
 * also we need to ask them for a name for the profile
 */
Generator.prototype._askIfDatabaseSelectionDone = function(cb , profile)
{
	var _this = this;
	var cb = this.async();
	var lang = _this.env.options.lang;
	var msg = (lang==='cn') ? '你想再建立另一个数据库的设定吗？' : 'Would you like to setup another database profile?';

	this.prompt({
		type: 'confirm',
		name: 'continueWithAnotherDatabase',
		message: msg,
		default: false
	} , function(props) {
		if (props.continueWithAnotherDatabase) {
			_this._askForConnectionName(cb);
		}
		else {
			cb();
		}
	});
};

/**
 * Ask the user if they want to setup the front end at this point
 */
Generator.prototype.askIfWantToSetupUI = function()
{
	var _this = this;
	var cb = this.async();
	var lang = _this.env.options.lang;
	// 在完成设置后台后
	var msg = (lang==='cn') ? '你想继续设置界面吗？' : 'Would you like to setup UI framework now?';
	this.prompt({
		type: 'confirm',
		name: 'setupUI',
		message: msg,
		default: true
	},function(props)
	{
		_this.answers.setupUI = props.setupUI;
		// this is where we need to call the ng-panes or riot-panes or react-panes
		cb();
	}.bind(this));
};

    //////////////////////////////
    //      UI SETUP            //
    //////////////////////////////

var angularLatestVersion = '1.4.5';

Generator.prototype.askForAngularVersion = function()
{
    var _this = this;
    var lang = this.env.options.lang;
    if (!this.previousProject) {
        if (this.answers.setupUI) {
            var cb = this.async();
            this.prompt({
                type: 'list',
                name: 'angularVersion',
                message: (lang==='cn') ? '你想用那个版本的AngularJS' : 'What version of AngularJS would you like to use',
                choices: [{name: 'V1.4.X' , value: angularLatestVersion},
                          {name: 'V1.3.X' , value: '1.3.18'},
                          {name: 'V2'     , value: '2.0.0'}],
                default: angularLatestVersion
            }, function(props) {
                if (props.angularVersion==='2.0.0') {
                    var msg = (lang==='cn') ? '现时只支技V.1.X版本，默认为'+angularLatestVersion+'版'
                                                              : 'Sorry only support V1.X at the moment. Default version set to ' + angularLatestVersion;
                    _this.log(chalk.red('\n'+msg+'\n'));
                    // @TODO in the future set this to the TypeScript
                    // _this.env.options.scriptingLang = 'TS';
                    _this.askForAngularVersion();
                }
                else {
                    _this.answers.angularVersion = _this.answers.angularVersion = props.angularVersion;
                    cb();
                }
            }.bind(this));
        }
    }
    else {
        _this.answers.angularVersion = _this.previousProject.angularVersion;
    }
};

/**
 * If its AngularJS 1.x then we ask for what type of scripting they want to use.
 * V2 default to TypeScript
 */
Generator.prototype.askForScriptingOptions = function()
{
    var _this = this;
    var lang = this.env.options.lang;
    if (!this.previousProject) {
        if (this.answers.setupUI) {
            var cb = this.async();
            var defaultValue = 'JS';
            var choices = [{name: 'Javascript' , value: 'JS'} ,
                           {name: 'CoffeeScript' , value: 'CS'},
                           {name: 'TypeScript' , value: 'TS'}];
            // AngularJS V.2 use TypeScript
            if (_this.answers.angularVersion==='V2') {
                _this.answers.scriptingLang = 'TS';
                _this.answers.coffee     = (lang === 'CS');
                _this.answers.typescript = (lang === 'TS');
            }
            else {
                this.prompt({
                    type: 'list',
                    name: 'scriptingLang',
                    message: (lang==='cn') ? '你想使用那种方式开发你的Javascript呢?': 'What script would you like to use to develop your app?',
                    choices: choices,
                    default: defaultValue
                }, function(props) {
                    var lang = props.scriptingLang;

                    _this.answers.scriptingLang = lang;
                    _this.answers.coffee     = (lang === 'CS');
                  	_this.answers.typescript = (lang === 'TS');

                    cb();
                }.bind(this));
            }
        }
    }
    else {
        var p = _this.previousProject;
        _this.answers.scriptingLang = p.scriptingLang;
        _this.scriptingLang = p.scriptingLang;
        _this.coffee     = (p.scriptingLang === 'CS');
        _this.typescript = (p.scriptingLang === 'TS');
    }
};

/**
 * we are going to list a few popular UI Frameworks to choose from
 */
Generator.prototype.askForUIFrameworks = function()
{
    var _this = this;
    var lang = this.env.options.lang;
    /**
     * This gives us an opportunity to call a remote to check on their latest version etc.
     * or a bit manually approach, then we could just update this part to keep it up to date.
     */
    var frameworks = [
        {name: 'Bootstrap' , value: 'bootstrap' , package: 'bootstrap' , ver: '~3.3.5' , alt: 'bootstrap-sass-official' , altver: '~3.3.5'},
        {name: 'Foundation', value: 'foundation' , package: 'foundation', ver : '~5.5.2'},
        {name: 'Semantic-UI', value: 'semantic' , package: 'semantic-ui', ver: '~2.1.3'},
        {name: 'Angular-Material' , value: 'material' , package: 'angular-material', ver: '~0.10.1'},
        {name: 'Materialize', value: 'materialize' , package: 'materialize' , ver: '~0.97.0'},
        {name: 'UIKit', value: 'uikit' , package: 'uikit', ver: '~2.21.0'}
    ];
    var amazeui = {name: 'AmazeUI' , value: 'amazeui' , package: 'amazeui' , ver: '~2.4.2'};
    (lang==='cn') ? frameworks.unshift(amazeui) : frameworks.push(amazeui);
    // this should not put into the answers
    _this.env.options.availableFrameworks = frameworks;

    if (!this.previousProject) {
        if (this.answers.setupUI) {
            var cb = this.async();
          	this.prompt([{
            	type: 'list',
            	name: 'uiframework',
            	message:  (lang==='cn') ? '你想使用那个界面库呢？': 'Which UI Framework would you like to use?',
                choices: _this.env.options.availableFrameworks,
            	default: (lang==='cn') ? 'amazeui' : 'bootstrap'
          	}], function (props) {
                _this.answers.uiframework = props.uiframework;
            	cb();
          	}.bind(this));
        }
    }
    else {
        _this.answers.uiframework = _this.previousProject.uiframework;
    }
};

/**
 * @TODO this should change to a list of SASS , LESS OR CSS
 *       if they want LESS we could use the JS version during DEV
 */
Generator.prototype.askForStyles = function()
{
  	var _this = this;
    var all = ['less' , 'sass' , 'css'];
    // we take the last value `framework` to determinen what they can use next
    var features = {
        'bootstrap' : ['LESS' , 'SASS'],
        'foundation' : ['SASS'],
        'semantic' : ['LESS'],
        'material' : ['SASS'],
        'materialize' : ['SASS'],
        'uikit' : ['LESS' , 'SASS'],
        'amazeui': ['LESS']
    };
    var framework = this.answers.uiframework;
    var choices = ['CSS'].concat( features[ framework ] );
    /**
     * util methods to set the rest to false
     */
    var _setTheRest = function(_this , features , framework)
    {
        _.each(features , function(value , feature) {
            if (feature===framework) {
                _this[feature] = true;
                return;
            }
            _this.answers[ feature ] = false;
            all.forEach(function(oscss) {
                _this.answers.cssConfig[feature + oscss] = false;
            });
        });
    };

    if (!this.previousProject) {
        if (this.answers.setupUI) {
            var cb = this.async();
            this.prompt([{
                type: 'list',
                name: 'styleDev',
                message: (this.env.options.lang==='cn') ? '你想使用那种方式开发你的CSS呢？' : 'How would you like to develop your style?',
                choices: choices,
                default: 'CSS'
            }], function(props) {
                var style = props.styleDev.toLowerCase();
                _this.answers.styleDev = _this.answers.styleDev = style;
                // we need to create a rather long variable for the template file as well
                _this.answers.cssConfig = _this.answers.cssConfig = _this.answers.cssFeatureEnabled = {};
                // set this up for the template
                all.forEach(function(oscss) {
                    _this.answers.cssConfig[ framework + oscss ] = (style===oscss); // = _this.answers.cssConfig[ framework + oscss ]
                    _this.answers[oscss] = (style===oscss); // = _this.answers.cssFeatureEnabled[oscss]
                });
                _setTheRest(_this , features , framework);
                cb();
            }.bind(this));
        }
    }
    else { // restore variables
        var p = _this.previousProject;
        _this.answers.styleDev = p.styleDev;
        _this.answers.cssConfig = p.cssConfig;
        all.forEach(function(oscss)
        {
            _this.answers[oscss] = p.cssFeatureEnabled[oscss];
        });
        _setTheRest(_this , features , framework);
    }
};

/**
 * asking for what module the user want to include in the app
 */
Generator.prototype.askForAnguar1xModules = function()
{
    var _this = this;
    var choices = [
        {value: 'animateModule', name: 'angular-animate.js', alias: 'ngAnimate', checked: true},
        {value: 'ariaModule', name: 'angular-aria.js', alias: 'ngAria', checked: false},
        {value: 'cookiesModule', name: 'angular-cookies.js', alias: 'ngCookies' , checked: true},
        {value: 'resourceModule', name: 'angular-resource.js', alias: 'ngResource', checked: true},
        {value: 'messagesModule', name: 'angular-messages.js', alias: 'ngMessage', checked: false},
        {value: 'routeModule', name: 'angular-route.js' , alias: 'ngRoute' , checked: true},
        {value: 'sanitizeModule', name: 'angular-sanitize.js', alias: 'ngSanitize', checked: true},
        {value: 'touchModule', name: 'angular-touch.js',alias: 'ngTouch',checked: true}
    ];
    var _setModules = function(angMods)
    {
        // inject the ngMaterial if the user choose angular-material for UI
        if (_this.uiframework==='material') {
            angMods.push('\'ngMaterial\'');
        }
        if (angMods.length) {
            _this.env.options.angularDeps = '\n    ' + angMods.join(',\n    ') + '\n  ';
        }
    };
    if (!this.previousProject) {
        if (this.answers.setupUI) {
            var cb = this.async();
          	var prompts = [{
            	type: 'checkbox',
            	name: 'modules',
            	message: (this.env.options.lang==='cn') ? '你想使用那个Angular的模塊呢？' : 'Which modules would you like to include?',
            	choices: choices
          	}];
            var _this = this;
            _this.answers.ngMods = {};
            this.prompt(prompts, function (props) {
            	var hasMod = function (mod) {
                    return props.modules.indexOf(mod) !== -1;
        		};
                var angMods = [];
                // start loop
                choices.forEach(function(_mod_) {
                    var modName = _mod_.value;
                    var yes = hasMod(modName);
                    if (yes) {
                        angMods.push( "'"+_mod_.alias+"'" );
                        if (modName==='routeModule') {
                            _this.answers.ngRoute = true;
                        }
                        _this[_mod_.value] = _this.answers.ngMods[_mod_.value] = true;
                    }
                    else {
                        _this[_mod_.value] = _this.answers.ngMods[_mod_.value] = false;
                    }
                });
                _setModules(angMods);
            	cb();
          	}.bind(this));
        }
    }
    else { // restore variables
        var p = _this.previousProject.ngMods;
        var allMods = {};
        var angMods = [];
        choices.forEach(function(mods)
        {
            allMods[mods.value] = mods;
        });
        _.each(p , function(enabled , modName)
        {
            _this[modName] = enabled;
            if (enabled) {
                if (modName==='routeModule') {
                    _this.answers.ngRoute = true;
                }
                angMods.push( "'" + allMods[modName].alias + "'" );
            }
        });
        _setModules(angMods);
    }
};




/**
 * ask the user if they want to save this to their project preference and re-use next time
 */

Generator.prototype._TODO_saveProjectToPreference = function()
{
    var _this = this;
	var cb = this.async();
	var lang = _this.env.options.lang;
    _this._displayProject();
	// 在完成设置后台后
	var msg = (lang==='cn') ? '你想把这个项目的设置保存吗?' : 'Would you like to save this project setting?';
    this.prompt({
        type: 'confirm',
        name: 'savePreference',
        message: msg,
        default: false
    },function(props) {
        _this.answers.savePreference = props.savePreference;
        cb();
    }.bind(this));
};

	////////////////////////////
	//	    RUNNING SETUP     //
	////////////////////////////

/**
 * we call this before the generate package.json see if that fix the conflict error
 * @TODO this generator is too old. Need to find a way to do it differently! 
 */
Generator.prototype._installKarmaApp = function()
{
    if (this.answers.setupUI) {
        var jsExt = this.answers.coffee ? 'coffee' : 'js';
        var bowerComments = [
            'bower:js',
            'endbower'
        ];
        if (this.options.coffee) {
            bowerComments.push('bower:coffee');
            bowerComments.push('endbower');
        }
        // this one keep trying to overwrite the package.json?
        this.composeWith('karma:app', {
            options: {
                'skip-install': false , //this.env.options['skip-install'],
                'base-path': '../',
                'coffee': this.coffee,
                'travis': true,
                'files-comments': bowerComments.join(','),
                'app-files': 'scripts/**/*.' + jsExt,
                'test-files': [
                    'test/mock/**/*.' + jsExt,
                    'test/spec/**/*.' + jsExt
                ].join(','),
                'bower-components-path': 'app/client/web/bower_components'
            }
        });
    }
};

/**
 * setting up the package.json file
 */
Generator.prototype.setupPackageJson = function()
{
	this.sourceRoot(path.join(__dirname, path.join('..','templates','root')));
	var _this = this;
	var nodeModules = [];

	var add = function(moduleName)
	{
		if (_.indexOf(nodeModules , moduleName) > -1) {
			return;
		}
		nodeModules.push(moduleName);
	};

	// this need to change completely
	_.each(_this.answers.databases , function(conf , profile)
	{
		if (conf.databaseType==='RDBMS') {
			add('"sequelize": "^3.5.1"');
		}
		switch (conf.databaseDriver) {
			case 'mongodb':
				add('"connect-mongo": "~0.4.1"');
				add('"mongoose": "~3.8.8"');
				add('"express-restify-mongoose": "~1.0.3"');
			break;
	        case 'couchdb':
	            add('"cradle": "~0.6.9"');
	        break;
	        case 'redis':
	        	add('"redis": "~1.0.0"');
	        break;
			case 'mysql':
			case 'mariadb':
				add('"mysql": "^2.8.0"');
			break;
			case 'postgresql':
				add('"pg": "^4.4.1"');
				add('"pg-hstore": "^2.3.2"');
			break;
			case 'sqlite':
				add('"sqlite3": "^3.0.10"');
			break;
			default:
				// no default
		}
	});
    this.extraNodeDevModules = false;
    // front end required for dev
    if (this.answers.setupUI) {
        var devModules = [];
        // oocss
        if (this.answers.sass) {
            devModules.push('\t"gulp-ruby-sass": "~0.4.3"');
            devModules.push('\t"gulp-sass": "~2.0.2"');
        }
        else if (this.answers.less) {
            devModules.push('\t"gulp-less":"~3.0.3"');
        }
        // scripting
        if (this.answers.coffee) {
            devModules.push('\t"gulp-coffeelint": "~0.5.0"');
            devModules.push('\n"gulp-coffee": "~2.3.1"');
        } else if (this.answers.typescript) {
            devModules.push('\t"gulp-typescript" : "~2.8.0"');
        }
        this.extraNodeDevModules = devModules.join(',\n');
    }

	// setup passport node modules
	this.answers.passportModules.forEach(function(mod)
	{
		nodeModules.push('"' + mod.module + '":"' + mod.ver + '"');
	});

	this.extraNodeModules = nodeModules.join(',\n');
	this.template('_package.json' , 'package.json');
};

/**
 * setup the root files
 */
Generator.prototype.setupRoot = function()
{
	var _this = this;
    var join = path.join;
	// package.json
	// this._setupPackageJson();
    // reset the path
    this.sourceRoot(join(__dirname, join('..','templates','root')));
	// copy hidden files
	this.copy('_editorconfig' , '.editorconfig');
    this.copy('_gitattributes' , '.gitattributes');
	this.copy('_jshintignore' , '.jshintignore');
    this.copy('_jshintrc' , '.jshintrc');
    this.copy('_yo-rc.json' , '.yo-rc.json');
    this.copy('_gitignore', '.gitignore');
	this.copy('_bowerrc' , '.bowerrc');
	// save our project settings here
	this._saveProjectSetting();
    // copy the gulp file - set a hook for the future to use coffee script
    if (!this.answers.setupUI) {
        this.answers.coffee = false;
	    this.template('_gulpfile.js' , 'gulpfile.js');
    }
    else {
        // use the front and backend gulpfile
        this.template('_gulpfile_extra.js' , 'gulpfile.js');
    }
	// the most important one
	this.copy('_start.js' , 'start.js');
	// now copy the deploy folder
	this.directory('deploy');
    // new deploy folder call
    this.appname = this.answers.appname;

    var features = ['production'];
    if (this.answers.virtual==='v') {
        features.push('development');
    }
    _.each(['nginx' , 'upstart'] , function(n1)
    {
        _.each(features , function(n2)
        {
            var filename = n1 + '.' + n2 + '.conf';
            _this.template(join('deploy' , filename)  , join('deploy' , filename));
        });
    });
};

/**
 * setup the application files
 */
Generator.prototype.setupServerFiles = function()
{
	var join = path.join;
	var _this = this;
	this.sourceRoot(join(__dirname, join('..' , 'templates' , 'common','app','server')));
	// controllers
	this.directory(join('app','server','controllers'));
	this.helloWorld = (this.env.options.lang==='cn') ? '世界你好!' : 'Hello world!';
	this.name = 'main';
	this.template(join('controllers','main.js') , join('app','server','controllers','main.ctrl.js'));
	// models
	this.directory(join('app' , 'server' , 'models'));

    this.classedName = 'User';
    // using the default profile to create the user
	this.template(join('models' , 'user' , this.answers.databases['default'].databaseType.toLowerCase() + '.js') , join('app', 'server','models','user.model.js'));

	// views
	var copy = function (dest) {
        this.copy(join('views' ,dest), join('app' , 'server' , 'views' , dest));
    }.bind(this);

	this.directory(join('app' , 'server'));
	this.directory(join('app' , 'server' , 'views'));

	copy('404.swig.html');
	copy('500.swig.html');
    // this need to change - when we are using setupUI
    if (this.answers.setupUI) {
        this.thisYear = new Date().getFullYear();
        this.copy(join('views' , 'templates' , this.answers.uiframework + '.html') ,
                  join('app' , 'server' , 'views' , 'index.swig.html'));
    }
    else {
        copy('index.swig.html');
    }
    copy('layout.swig.html');
};

/**
 * just setting up our panes.js lib files
 */
Generator.prototype.setupLibFiles = function()
{
	var _this = this;
	var join = path.join;
	_this.sourceRoot(join(__dirname, join('..','templates','common')));
	// our panes.js lib folder
	_this.directory('lib');
	['controller' , 'express' , 'methods' , 'model' , 'panes' , 'socket' , 'connection'].forEach(function(file)
	{
		file = file + '.js';
		_this.copy(join('lib' , file) , join('lib' , file));
	});
};

/**
 * setup config folder files
 */
Generator.prototype.setupConfigFiles = function()
{
	var join = path.join;
	var _this = this;
	this.sourceRoot(join(__dirname , join('..' , 'templates' , 'common', 'app' , 'config' , 'system')));
	var copy = function (dest , another) {
		another = another || dest;
        this.copy(join(dest + '.js'), join(another + '.js'));
    }.bind(this);
	this.directory(join('app','config'));
	this.directory(join('app','config','opt'));
	this.directory(join('app','config','strategies'));
    this.directory(join('app','config','system'));
	// this copy to the wrong place
	['config' , 'init' , 'logger' , 'passport' , 'services'].forEach(function(file)
	{
		copy(file , join('app','config','system' , file));
	});
    //  'routes' ,  files need to do this differently
    this.routesDefinition = JSON.stringify({ '/' : {controller: 'main' , action: 'index'} });
    this.template('routes.js' , join('app' , 'config' , 'system' ,'routes.js'));

	this.lang = this.env.options.lang;

};

/**
 * generate the passport strategies config
 */
Generator.prototype.setPassportConfig = function()
{
    var _this = this;
	var configModules = [];
    // reset the root path
    this.sourceRoot(path.join(__dirname , path.join('..' , 'templates' , 'common', 'app' , 'config')));

    this.answers.passportModules.forEach(function(mod)
	{
		configModules.push('\n\t' + mod.value + ':' + passportConfig.getStrategiesConfig(mod.value));
	});
	this.configPassportStrategies = configModules.join(',\n');
	// setup the strategies
	this.answers.passportModules.forEach(function(mod)
	{
		_this.copy(path.join('strategies' , mod.value + '.js') , path.join('app' , 'config' , 'strategies' , mod.value + '.js'));
	});
};

/**
 * construct the database config file values
 */
Generator.prototype.setDatabaseConfig = function()
{
	var _this = this;
	var join = path.join;

	this.databaseConfigJSON = databaseConfig.generate(this.answers.virtual , this.answers.databases);

	// since we only want to create it once, create a top level switch
	if (_this.answers.useSqlite===true) { // create a folder to store the db file
		_this.directory('data');
	}

	// the files
	this.jwtSecret = password.generate(6);
	this.template(join('opt' , 'base.js') , join('app' , 'config' , 'opt' , 'base.js'));
	this.template(join('opt' , 'development.js') , join('app' , 'config' , 'opt' , 'development.js'));
	this.jwtProductionSecret = password.generate(12);
	this.template(join('opt' , 'production.js') , join('app' , 'config' , 'opt' , 'production.js'));
	this.template(join('opt' , 'test.js') , join('app' , 'config' , 'opt' , 'test.js'));
};

/**
 * setup the virtual server if the user pick one
 */
Generator.prototype.setupVirtualServer = function()
{
	// see if we want to create Docker or Vagrant
    var t = this;
	var v = this.answers.virtual;
    var join = path.join;

    if (v !== 'n') {
        t.answers.install = {};
        var dbs = ['MySQL' , 'MariaDB' , 'Postgresql' , 'SQLite' , 'MongoDB' , 'CouchDB' , 'Redis'];
        var selected = [];
        _.each(t.answers.databases , function(db)
        {
            selected.push(db.databaseDriver);
        });
        _.each(dbs , function(db)
        {
            var _db = db.toLowerCase();
            t.answers.install[ _db ] = selected.indexOf(_db) > -1 ? true : false;
        });
        this.sourceRoot(join(__dirname, join('..','templates','root')));
    }

	if (v==='d') {
        // @TODO
		this.copy('_Dockerfile' , 'Dockerfile');
		this.copy('_docker-compose.yml' , 'docker-compose.yml');
	}
	else if (v==='v') {
		this.directory('vagrant');

		this.copy(join('vagrant','README.md')    , join('vagrant','README.md'));
		this.copy(join('vagrant','Vagrantfile')  , join('vagrant','Vagrantfile'));

        // generate the provison.sh file for vagrant
        this.template(join('vagrant','provision.sh') , join('vagrant','provision.sh'));
    }
};

/**
 * set up the web folder files - if the user choose not to setup the UI framework
 */
Generator.prototype.setupWebFiles = function()
{
	var join = path.join;
	this.sourceRoot(join(__dirname, join('..','templates','common','app','client')));
	this.directory(join('app' , 'client'));
	this.directory(join('app' , 'client' , 'web'));

	this.copy(join('web','humans.txt') , join('app' , 'client' , 'web' , 'human.txt'));
	this.copy(join('web','robots.txt') , join('app' , 'client' , 'web' , 'robot.txt'));

    // setup empty folder in case the search fail and complain!
    this.directory(join('app' , 'client' , 'web' , 'scripts'));
    this.directory(join('app' , 'client' , 'web' , 'styles'));
    // reset the source root
    this.sourceRoot(join(__dirname, join('..','templates','root')));

	if (!this.answers.setupUI) {
		this.bowerDependencies = false;
	}
    else {
        // generate client side bower deps here
        this._overRidesBower();
    }
    this.template('_bower.json' , 'bower.json');
    // copy files over to client side
    if (this.answers.setupUI) {
        this._setupUIFiles();
    }
};

	///////////////////////////////
	//		PRIVATE METHODS      //
	///////////////////////////////

/**
 * adding bower overrides property
 */
Generator.prototype._overRidesBower = function()
{
    this.bowerDependencies = true;
    this.overwriteBower = false;
    this.ngVer = this.answers.angularVersion; // move back from template - we could do that in the remote in the future
    // @TODO replace this hack with something better please ...
    this.modVer = '2.8.3';

    var f = _.findWhere(this.env.options.availableFrameworks , {value: this.answers.uiframework});

    if (this.uiframework==='bootstrap' && this.answers.styleDev==='sass') {
        this.bowerUIFramework = f.alt;
        this.bowerUIFrameworkVer = f.altver;
    }
    else {
        this.bowerUIFramework = f.package;
        this.bowerUIFrameworkVer = f.ver;
    }

    var _this = this,
        style = _this.answers.styleDev,
        fontFolder = [],
        files = [];

    switch (_this.uiframework) {
        case 'bootstrap':
            files = (style==='sass') ? ['assets/javascript/bootstrap.js'] : ['dist/js/bootstrap.js'];
            if (style!=='sass') {
                files.push('dist/css/bootstrap.css');
            }
            if (style!=='css') {
                fontFolder = (style==='sass') ? ['assets' , 'fonts' , 'bootstrap'] : ['fonts'];
            }
        break;
        case 'amazeui':
            files = ['dist/js/amazeui.js'];
            files.push('dist/css/amazeui.css');
            if (style !=='css') {
                fontFolder = ['fonts'];
            }
        break;
        case 'foundation':
            files = ['js/foundation.js'];
            files.push('css/foundation.css');
        break;
        case 'semantic':
            files = ['dist/semantic.js'];
            files.push('dist/semantic.css');
            if (style!=='css') {
                fontFolder = ['dist','themes','default','assets','fonts'];
            }
        break;
        case 'materialize':
            files = ['dist/js/materialize.js'];
            files.push('dist/css/materialize.css');
            if (style!=='css') {
                fontFolder = ['font'];
            }
        break;
        case 'uikit':
            files = ['dist/js/uikit.js'];
            files.push('dist/css/uikit.css');
            if (style!=='css') {
                fontFolder = ['fonts'];
            }
        break;
        case 'material':
            files = ['angular-material.js'];
            files.push('angular-material.css');
        break;
        default:
            // there is nothing to do here, just to keep jshint happy
    }

    this.answers.fontFolder = fontFolder;

    if (files.length>0) {
        var fw = _this._getUIFramework(_this.uiframework);
        var uiFrameworkName = fw.package;
        if (uiFrameworkName==='bootstrap' && style==='sass') {
            uiFrameworkName = fw.alt;
        }
        var ow = '\n\t\t"' + uiFrameworkName + '": {\n\t\t\t"main": ["';
            ow += files.join('","');
            ow += '"]\t\n\t\t}\n';
        _this.overwriteBower = ow;
    }
};

/**
 * once bower is done, we need to copy the ng-files in place
 */
Generator.prototype._setupUIFiles = function()
{
    var join = path.join;
    var _this = this;
    var ext = _this.answers.styleDev;
    // set the source root path
    _this.sourceRoot(join(__dirname, join('..','templates','common' , 'app' , 'client' , 'common' , 'app')));
    // this is the client folder path!
    var web = join(_this.appPath , 'client' , 'web');
    _.each(_this.answers.cssConfig , function(val , key) {
        _this[key] = val;
    });
    // copy css / less or sass file
  	var cssFile = 'styles/main.' + (ext==='sass' ? 'scss' : ext);

    _this.copy(join(cssFile),  join(web , cssFile));
    // copy images
    //this.copy(join('app','images','panesjs.png') , join(web , 'images' , 'panesjs.png'));
    _this.directory(join('app', 'images'), join(web , 'images'));

    _this.copy(join('favicon.ico') , join(web , 'favicon.ico'));
    // copy template
    _this.copy(join('views' , 'main.html') , join(web , 'views' ,'main.html'));
    _this.copy(join('views' , 'view.html') , join(web , 'views' ,'view.html'));

    _this._setupAngularApp(web);
};
/**
 * breaking out setupUIFile since its about seeing the angular files anyway
 */
Generator.prototype._setupAngularApp = function(web)
{
    this.sourceRoot(path.join(__dirname, path.join('..','templates','common' , 'app' , 'client')));
    // setting up the angular app with composeWith (calling our own sub generators)
    var args = ['main'],
        opts = {
            coffee: this.answers.scriptingLang === 'CS',
            typescript: this.answers.scriptingLang === 'TS'
        },
        scriptSuffix = 'js',
        scriptPath = 'javascript';
    if (opts.coffee) {
        scriptSuffix = 'coffee';
        scriptPath = 'coffeescript';
    }
    else if (opts.typescript) {
        scriptSuffix = 'ts';
        scriptPath = 'typescript';
    }

    // generate the app.js file manually here and get rip of the ng-app sub generator
    this.angularModules = this.answers.ngMods.angularDeps;
    this.ngCookies = this.answers.ngMods.ngCookies;
    this.ngResource = this.answers.ngMods.ngResource;
    this.ngSanitize = this.answers.ngMods.ngSanitize;
    this.ngRoute = this.answers.ngMods.ngRoute;

    this.copy(path.join( scriptPath , 'app.' + scriptSuffix ) , path.join(web , 'scripts' , 'app.' + scriptSuffix));

    // setup the main controller
  	this.composeWith('panesjs:ng-controller', {
    	args: args,
        options: opts
  	});

};

/**
 * fetching the appName
 */
Generator.prototype._getAppName = function(appName)
{
  	if (!appName) {
		this.baseNameOption = false;
		if (!this.appname) {
			this.appname = path.basename(process.cwd());
			this.baseNameOption=true;
		}
	}
	else {
		this.appname = appName;
		this.baseNameOption = false;
	}
    this.appTplName =  _.slugify( _.humanize(this.appname) );
    this.scriptAppName = _.camelize(this.appname) + ngUtils.appName(this);
    // the appname got lost somewhere down there.
    this.env.options.appNameAgain = this.answers.appname = this.appname;
    this.env.options.appTplName = this.answers.appTplName = this.appTplName;
	this.env.options.scriptAppName = this.answers.scriptAppName = this.scriptAppName;
};
/**
 * setup the other options
 */
Generator.prototype._setupOptions = function()
{
	// lang options
    this.option('cn' , {
        desc: 'Change to Chinese (使用中文版)',
        type: String
    });
    var lang = (this.options['cn']) ? 'cn' : 'en';
    this.env.options.lang = lang;
    /*
    @TODO for managing projects
    this.option('projects' {
        desc: '',
        type: String
    });
    */
  	if (typeof this.env.options.appPath === 'undefined') {
    	if (!this.env.options.appPath) {
      		try {
        		this.env.options.appPath = require(path.join(process.cwd(), 'bower.json')).appPath;
      		} catch (e) {}
    	}
    	this.env.options.appPath = this.env.options.appPath || 'app';
    	this.options.appPath = this.env.options.appPath;
  	}
	// not allow changing the appPath!
  	this.appPath = this.env.options.appPath;

};

/**
 * @TODO do the setup stuff here!
 */
Generator.prototype._runFinalSetup = function()
{
	var _this = this;
	var lang = _this.env.options.lang;
    if (!_this.options['skip-install']) {
		var beginning = (lang==='cn') ? '下载中' : 'Downloading';
        var command = ((lang==='cn' && isInstalled('cnpm')) ? 'cnpm' : 'npm') + ' install && bower install';
		var bm = (lang==='cn') ? '正在执行 '+command+' 指令，请去上个厕所，抽根煙，弄杯咖啡，補補妆，打电话给你爸妈 ... 回来时任务应该完成了。'
                               : 'Running '+command+', go get yourself a coffee, go to the toilet, powder your nose , call your mom ... it will be ready when you are back.';
        var dotting = new Dot({
                beforeMsg: bm,
                beginning: beginning
            });
        // execute command
        exec(command , function(error) {
            dotting.finish();
            if (error !== null) {
                var errorMsg = (lang==='cn') ? '下载出错了 >_< 请再次运行`'+command+'`指令' : 'package install error, please re-run `'+command+'` again!';
                _this.log.error(errorMsg);
                _this.log.error(error);
            }
            else {
                // completed
                var finalMsg = (lang==='cn') ? '任务完成，所有外加插件下载成功。' : 'Phew, deps are all downloaded.';
                _this.log(chalk.yellow(finalMsg));
                _this.env.options.installing  = false;
                // there is a problem here - when the database is not yet setup and the vagrant not setup
                // this will throw a whole lot of errors, so we only call this when the user is not using Docker or Vagrant and database is using sqlite
                if (_this.answers.virtual==='n' && _this.answers.databaseDriver==='sqlite') {
                    // go straight to gulp
                    _this.spawnCommand('gulp' , ['serve']);
                }
                else {
                    var msg = '----------------------------------------------------------\n';
                    if (_this.answers.virtual!=='n') {
                        if (lang==='cn') {
                            msg += '你选了' + chalk.yellow((_this.answers.virtual==='v' ? 'Vagrant' : 'Docker'))  + '来協助你的开发.\n';
                        }
                        else {
                            msg += 'You have selected to use ' + chalk.yellow((_this.answers.virtual==='v' ? 'Vagrant' : 'Docker')) + ' for development.\n';
                        }
                    }
                    var dbs = [];
                    _.forEach(_this.answers.databases , function(conf) {
                        dbs.push(conf.databaseDriver);
                    });
                    var dbNames = dbs.join(',');
                    if (lang==='cn') {
                        msg += '你选了' + chalk.yellow(dbNames) + '数据库,\n';
                        msg += '请先把以上提及的项目先设立后再行\'gulp serve\'来起动你的项目.';
                    }
                    else {
                        msg += 'You have picked ' + chalk.yellow(dbNames) + ' as your database,\n';
                        msg += 'Please set them up before use \'gulp serve\' to start the application.';
                    }
                    msg += '\n----------------------------------------------------------\n';
                    if (lang==='cn') {
                        msg += '你可以在你的项目根目錄里运行'+ chalk.yellow('\'yo panesjs --help\'') + '来查看所有可用指令.';
                    }
                    else {
                        msg += 'You can also issue ' + chalk.yellow('\'yo panesjs --help\'') + ' from your project root to view all available options.';
                    }
                    _this.log(msg);
                }
            }
        });
    }
    // this never works!
    this.config.save('lang' , this.lang);
};


/**
 * Save this project setting for later use
 * Also save this to the user project preference file at the same time!
 */
Generator.prototype._saveProjectSetting = function()
{
	var _this = this;
	var json = _.extend({} , _this.answers); // need to create a new object for overwrite
	var passportSelectedModules = [];

	_this.answers.passportModules.forEach(function(mod)
	{
		passportSelectedModules.push(mod.value);
	});

	json.passportModules = passportSelectedModules;

	_this.passportSelectedModules = '"' + passportSelectedModules.join('","') + '"';
	_this.answers.lang = _this.env.options.lang;
    // the databases config change as well
    _this.answers.databaseJSON = JSON.stringify(_this.answers.databases , null , 4);
    _this.clientSettings = false;
    if (_this.answers.setupUI) {
        var cSettings = {};
        var searches = ['angularVersion' , 'scriptingLang' , 'coffee' , 'typescript' , 'uiframework' , 'styleDev' , 'ngMods' , 'ngRoute'];
        _.each(_this.answers , function(d , k)
        {
            if (_.indexOf(searches , k) > -1) {
                cSettings[k] = d;
            }
        });
        _this.clientSettings = JSON.stringify(cSettings , null , 4);
    }
	_this.copy('_panesjs' , '.panesjs-config.json');
	// now save this to the user preference - if they select to save it
    // @TODO - do we want to enable this feature here as well? TBD
    if (_this.answers.savePreference) {
    	preference.save(json , function(err)
    	{
    		if (err) {
    			_this.log.error(err);
    		}
    	});
    }
};

// -- EOF --
